#
# Copyright (c) 2012 Philip Jameson
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions are met:
#
#  - Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#  - Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation 
#    and/or other materials provided with the distribution.
#  - Neither the name of the <ORGANIZATION> nor the names of its contributors 
#    may be used to endorse or promote products derived from this software 
#    without specific prior written permission.
#
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
# THE POSSIBILITY OF SUCH DAMAGE.
#

import time
import json
import re

from apnssocket import ApnsSocket
from config import Config

"""
This class is a simple handler class for the XMLRPC Server.
"""
class RPCHandler:
	"""Make sure the server is alive"""
	def ping(self):
		return 'pong'

	"""
	Queues a message for delivery to devices
	
	Args:
		application: The name of the application as defined in the configuration file
		devices: A list of device tokens given as strings
		message: Either a dictionary/associative array object, or a JSON string to send to apple
			 See here: http://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1
		expiry: Unix timestamp when the message should be expired. Defaults to 0, which means just deliver the message
		identifier: An identifier to use for this batch of messages. If apple reports an error, this will be passed into your error handler

	Returns:
		A (bool, string) tuple. If the message was queued, the first element is True
		Otherwise, the first element is False, and the second element gives a description of the problem

	"""
	def message(self, application, devices, message, expiry = 0, identifier = 1):
		Config.logger.debug('Received message via XMLRPC: %s' % message)
		if application not in Config.applications:
			return (False, 'Invalid application requested')

		try:
			expiry = int(expiry)
			if expiry != 0:
				if expiry < int(time.time()):
					return (False, 'Cannot expire message in the past')
				elif expiry > 0xFFFFFFFF:
					return (False, 'Expiry date too far in the future')
		except ValueError:
			return (False, 'Invalid expiry date')

		try:
			identifier = int(identifier)
			if identifier != 0:
				if identifier < 0:
					return (False, 'Invalid message identifier')
				elif identifier > 0xFFFFFFFF:
					return (False, 'Invalid message identifier')
		except ValueError:
			return (False, 'Invalid message identifier')

		if not isinstance(devices, list):
			return (False, 'Devices must be a list of device tokens')
		else:
			for device in devices:
				#Valid Apple device ids are 64 hex characters (32 bytes)
				if not re.match('^[a-f0-9]{64}$', device):
					return (False, 'Invalid device token provided')

		if isinstance(message, str):
			try:
				obj = json.loads(message)
				strMessage = json.dumps(obj) #Kill whitespace and such. We're space constrained here!
			except Exception, e:
				Config.logger.debug('Invalid json payload received. Exception was: %s'  % str(e))
				return (False, 'Invalid message. It must be a valid JSON object')
		else:
			strMessage = json.dumps(message)

		if len(strMessage) > ApnsSocket.APNS_MAX_MESSAGE_LENGTH:
			return (False, 'Invalid message. The maximum payload size is %s bytes' % ApnsSocket.APNS_MAX_MESSAGE_LENGTH)

		if ApnsSocket.queueMessage(application, devices, strMessage, expiry, identifier):
			return (True, 'Queued message')
		else:
			return (False, 'Could not queue message')
